﻿'TODO: Document Status Light
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel
Imports System.ComponentModel.Design

<DefaultEvent("Click"), ToolboxBitmap(GetType(StatusLight), "StatusLight.bmp")> _
Public Class StatusLight
#Region "Defaults"
    Private Const def_SupportTransparency As Boolean = False
    Private def_BackColor As Color = Color.Transparent

    Private def_Padding As New Padding(0, 0, 0, 0)

    Private def_OffColor As Color = Color.Gray

    Private def_ThresholdOn_Value As Double = 0
    Private def_ThresholdOn_Color As Color = Color.Green
    Private def_Threshold1_Value As Double = 50
    Private def_Threshold2_Value As Double = 75
    Private def_Threshold1_Color As Color = Color.Orange
    Private def_Threshold2_Color As Color = Color.Red
#End Region

#Region "Private Atributes"
    Private _SupportTransparency As Boolean = def_SupportTransparency
    Private _BackColor As Color = def_BackColor

    Private _OffColor As Color = def_OffColor

    Private WithEvents _ThresholdOn As Threshold
    Private WithEvents _Threshold1 As Threshold
    Private WithEvents _Threshold2 As Threshold


    Private WithEvents _Border As BorderType

    Private _Value As Double = -1
#End Region

#Region "Hidden Properties"
    <Browsable(False)> _
    Shadows Property BackgroundImage() As Color
        Get
        End Get
        Set(ByVal value As Color)
        End Set
    End Property
    <Browsable(False)> _
    Shadows Property BackgroundImageLayout() As Color
        Get
        End Get
        Set(ByVal value As Color)
        End Set
    End Property
    <Browsable(False)> _
   Shadows Property Font() As Font
        Get
            Return Nothing
        End Get
        Set(ByVal value As Font)
        End Set
    End Property
    <Browsable(False)> _
    Shadows Property BorderStyle() As BorderStyle
        Get
        End Get
        Set(ByVal value As BorderStyle)
        End Set
    End Property
    <Browsable(False)> _
    Shadows Property ForeColor() As Color
        Get
        End Get
        Set(ByVal value As Color)
        End Set
    End Property
#End Region

#Region "Property Change Event Handlers"
    Private Sub _Border_PropertyChanged(ByVal PropertyName As String) Handles _Border.PropertyChanged
        Me.Invalidate()
    End Sub
    Private Sub _Threshold1_PropertyChanged(ByVal PropertyName As String) Handles _Threshold1.PropertyChanged
        Me.Invalidate()
    End Sub
    Private Sub _Threshold2_PropertyChanged(ByVal PropertyName As String) Handles _Threshold2.PropertyChanged
        Me.Invalidate()
    End Sub
    Private Sub _ThresholdOn_PropertyChanged(ByVal PropertyName As String) Handles _ThresholdOn.PropertyChanged
        Me.Invalidate()
    End Sub
#End Region

#Region "Public Interface"
    Public Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        Me.SetStyle(ControlStyles.ResizeRedraw, True)
        Me.SetStyle(ControlStyles.UserPaint, True)
        SetTransparencyMode(False)
        Me.BackColor = def_BackColor

        _ThresholdOn = New Threshold(def_ThresholdOn_Value, def_ThresholdOn_Color)
        _Threshold1 = New Threshold(def_Threshold1_Value, def_Threshold1_Color)
        _Threshold2 = New Threshold(def_Threshold2_Value, def_Threshold2_Color)
        _Border = New BorderType()
    End Sub

    <Category("Gauge"), _
            Description("Sets the Light color when Value < OffValue"), _
            DefaultValue(GetType(System.Drawing.Color), "Gray")> _
        Public Property OffColor() As Color
        Get
            Return _OffColor
        End Get
        Set(ByVal value As Color)
            _OffColor = value
            Invalidate()
        End Set
    End Property

    <Category("Appearance"), TypeConverter(GetType(BorderTypeConverter))> _
    Public Property Border() As BorderType
        Get
            Return _Border
        End Get
        Set(ByVal value As BorderType)
            _Border = value
            Me.Invalidate()
        End Set
    End Property
    Public Sub ResetBorder()
        _Border.Reset()
    End Sub
    Public Function ShouldSerializeBorder() As Boolean
        Return _Border.ShouldSerialize()
    End Function


    <Category("Gauge"), TypeConverter(GetType(ThresholdTypeConverter))> _
    Public Property ThresholdOn() As Threshold
        Get
            Return _ThresholdOn
        End Get
        Set(ByVal value As Threshold)
            _ThresholdOn = value
            Me.Invalidate()
        End Set
    End Property
    Public Sub ResetThresholdOn()
        _ThresholdOn.Reset()
    End Sub
    Public Function ShouldSerializeThresholdOn() As Boolean
        Return _ThresholdOn.ShouldSerialize()
    End Function

    <Category("Gauge"), TypeConverter(GetType(ThresholdTypeConverter))> _
    Public Property Threshold1() As Threshold
        Get
            Return _Threshold1
        End Get
        Set(ByVal value As Threshold)
            _Threshold1 = value
            Me.Invalidate()
        End Set
    End Property
    Public Sub ResetThreshold1()
        _Threshold1.Reset()
    End Sub
    Public Function ShouldSerializeThreshold1() As Boolean
        Return _Threshold1.ShouldSerialize()
    End Function

    <Category("Gauge"), TypeConverter(GetType(ThresholdTypeConverter))> _
    Public Property Threshold2() As Threshold
        Get
            Return _Threshold2
        End Get
        Set(ByVal value As Threshold)
            _Threshold2 = value
            Me.Invalidate()
        End Set
    End Property
    Public Sub ResetThreshold2()
        _Threshold2.Reset()
    End Sub
    Public Function ShouldSerializeThreshold2() As Boolean
        Return _Threshold2.ShouldSerialize()
    End Function

    <Category("Gauge"), _
    Description("Sets the gauge Value."), _
    DefaultValue(-1.0#)> _
Public Property Value() As Double
        Get
            Return _Value
        End Get
        Set(ByVal value As Double)
            _Value = value
            Invalidate()
        End Set
    End Property

    Shadows Property BackColor() As Color
        Get
            Return MyBase.BackColor
        End Get
        Set(ByVal value As Color)
            If value = Color.Transparent Then
                SetTransparencyMode(True)
            Else
                SetTransparencyMode(False)
            End If
            MyBase.BackColor = value
        End Set
    End Property
    Shadows Sub ResetBackColor()
        BackColor = def_BackColor
    End Sub
    Shadows Function ShouldSerializeBackColor() As Boolean
        Return Not (BackColor.Equals(def_BackColor))
    End Function

    Shadows Property Padding() As Padding
        Get
            Return MyBase.Padding
        End Get
        Set(ByVal value As Padding)
            MyBase.Padding = value
            If Me.Parent Is Nothing Then
                Me.Refresh()
            Else
                Me.Parent.Refresh()
            End If
        End Set
    End Property
    Public Sub ResetPadding()
        Padding = def_Padding
    End Sub
    Public Function ShouldSerializePadding() As Boolean
        Return Not (Padding.Equals(def_Padding))
    End Function
#End Region

#Region "Transparency Support"
    Private Const WS_EX_TRANSPARENT As Int16 = &H20

    Friend Sub SetTransparencyMode(ByVal IsOn As Boolean)
        If IsOn Then
            'Support transparency - turn off double-buffering
            Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, False) 'Turn double-buffering off for transparency
            Me.SetStyle(ControlStyles.SupportsTransparentBackColor, True)
            Me.SetStyle(ControlStyles.Opaque, True)
        Else
            'No transparency support - enable double-buffering
            Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True) 'Turn double-buffering off for transparency
            Me.SetStyle(ControlStyles.SupportsTransparentBackColor, False)
            Me.SetStyle(ControlStyles.Opaque, False)
        End If
        If Me.Parent Is Nothing Then
            Me.Refresh()
        Else
            Me.Parent.Refresh()
        End If
    End Sub
    Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or WS_EX_TRANSPARENT
            Return cp
        End Get
    End Property
#End Region

#Region "Private Methods"
    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)

        UpdateLayout(e.Graphics)
    End Sub
    Private Sub UpdateLayout(ByRef myG As Graphics)
        Dim ClientRect As Rectangle = Me.DisplayRectangle

        Dim C As Single = Math.Min(ClientRect.Width, ClientRect.Height) * _Border.ChromeWidth / 100
        If _Value < _ThresholdOn.Value Then
            DrawLight(myG, CType(ClientRect, RectangleF), _OffColor, C, _Border.RimWidth)
        ElseIf _Value >= _Threshold2.Value Then
            DrawLight(myG, CType(ClientRect, RectangleF), _Threshold2.Color, C, _Border.RimWidth)
        ElseIf _Value >= _Threshold1.Value Then
            DrawLight(myG, CType(ClientRect, RectangleF), _Threshold1.Color, C, _Border.RimWidth)
        Else
            DrawLight(myG, CType(ClientRect, RectangleF), _ThresholdOn.Color, C, _Border.RimWidth)
        End If
    End Sub
    Private Sub DrawLight(ByRef myG As Graphics, ByRef myRect As RectangleF, ByVal OnColor As Color, ByVal ChromeWidth As Single, ByVal BorderWidth As Single)
        myG.SmoothingMode = SmoothingMode.AntiAlias

        Dim myRectF As RectangleF = Nothing
        Dim myPen As Pen = Nothing
        Dim myBrush As LinearGradientBrush = Nothing

        'Center point of drawing surface
        Dim Cx As Single = CSng(myRect.X) + (CSng(myRect.Width) / 2)
        Dim Cy As Single = CSng(myRect.Y) + (CSng(myRect.Height) / 2)

        Dim CenterF As New PointF(Cx, Cy)

        'Determine max radius of contained circle
        Dim maxR As Single = Math.Min(CSng(myRect.Width), CSng(myRect.Height)) / 2 - 2


        'Draw Gradient Fill between two rims
        Dim BetweenColor1 As Color = Color.White
        Dim BetweenColor2 As Color = Color.Black
        Dim BetweenWidth As Single = ChromeWidth '5.0!
        Dim BetweenRadius As Single = maxR
        myRectF = WidthAdjustedRectF(CenterF, BetweenRadius, 0)
        If myRectF.Height <= 0 OrElse myRectF.Width <= 0 Then Exit Sub 'Nothing to draw - bail out.
        myBrush = New LinearGradientBrush(myRectF, BetweenColor1, BetweenColor2, 25.0!)

        Dim colorBlend As New ColorBlend()
        'colorBlend.Colors = New Color() {Color.Black, Color.Silver, Color.Silver, Color.White}
        colorBlend.Colors = New Color() {Color.Black, _Border.ChromeColor, _Border.ChromeColor, Color.White}
        colorBlend.Positions = New Single() {0.0!, 0.1!, 0.5!, 1.0!}
        myBrush.InterpolationColors = colorBlend

        myG.FillEllipse(myBrush, myRectF)

        'Draw Outer Rim
        Dim OuterRimColor As Color = _Border.RimColor
        Dim OuterRimWidth As Single = BorderWidth '2.0!
        Dim OuterRimRadius As Single = maxR
        myRectF = WidthAdjustedRectF(CenterF, maxR, OuterRimWidth)
        myPen = New Pen(OuterRimColor, OuterRimWidth)
        myPen.Alignment = PenAlignment.Center
        myG.DrawEllipse(myPen, myRectF)



        'Draw Inner Rim
        Dim InnerRimColor As Color = Color.White 'OuterRimColor
        Dim InnerRimWidth As Single = BorderWidth '2.0! 'OuterRimWidth
        Dim InnerRimRadius = BetweenRadius - BetweenWidth
        myRectF = WidthAdjustedRectF(CenterF, InnerRimRadius, InnerRimWidth)
        myPen = New Pen(InnerRimColor, InnerRimWidth)
        myPen.Alignment = PenAlignment.Center
        'myG.DrawEllipse(myPen, myRectF)

        'Draw "Light" circle
        Dim LightRadius As Single = InnerRimRadius - InnerRimWidth
        myRectF = WidthAdjustedRectF(CenterF, LightRadius, 0)
        myBrush = New LinearGradientBrush(myRectF, OnColor, OnColor, 25.0!)
        colorBlend = New ColorBlend()
        colorBlend.Colors = New Color() {Color.White, Color.White, OnColor, Color.Black, Color.Black}
        colorBlend.Positions = New Single() {0.0!, 0.15!, 0.3!, 0.99!, 1.0!}
        myBrush.InterpolationColors = colorBlend
        myG.FillEllipse(myBrush, myRectF)
    End Sub
    Private Function WidthAdjustedRectF(ByVal Center As PointF, ByVal Radius As Single, ByVal BorderWidth As Single) As RectangleF
        Dim b2 As Single = BorderWidth / 2
        Dim Top As New PointF(Center.X - Radius + b2, Center.Y - Radius + b2)
        Dim Size As New SizeF(2 * Radius - BorderWidth, 2 * Radius - BorderWidth)
        Return New RectangleF(Top, Size)
    End Function
#End Region


#Region "Threshold"
    Public Class Threshold
        Private def_Value As Double
        Private def_Color As Color

        Private _Value As Double
        Private _Color As Color

        Public Event PropertyChanged(ByVal PropertyName As String)

        Friend Sub New()
            def_Value = Nothing
            def_Color = Nothing
            _Value = Nothing
            _Color = Nothing
        End Sub
        Friend Sub New(ByVal DefaultValue As Double, ByVal DefaultColor As Color)
            def_Value = DefaultValue
            _Value = DefaultValue
            def_Color = DefaultColor
            _Color = DefaultColor
        End Sub

        Public Property Value() As Double
            Get
                Return _Value
            End Get
            Set(ByVal value As Double)
                _Value = value
                RaiseEvent PropertyChanged("Value")
            End Set
        End Property
        Public Sub ResetValue()
            Value = def_Value
        End Sub
        Public Function ShouldSerializeValue() As Boolean
            Return Not (Value.Equals(def_Value))
        End Function

        Public Property Color() As Color
            Get
                Return _Color
            End Get
            Set(ByVal value As Color)
                _Color = value
                RaiseEvent PropertyChanged("Color")
            End Set
        End Property
        Public Sub ResetColor()
            Color = def_Color
        End Sub
        Public Function ShouldSerializeColor() As Boolean
            Return Not (Color.Equals(def_Color))
        End Function

        Public Sub Reset()
            _Value = def_Value
            _Color = def_Color
            RaiseEvent PropertyChanged("All")
        End Sub
        Public Function ShouldSerialize() As Boolean
            If Value.Equals(def_Value) AndAlso Color.Equals(def_Color) Then
                Return False
            Else
                Return True
            End If
        End Function

        Public Overrides Function ToString() As String
            Return "(Threshold)"
        End Function
    End Class
    Public Class ThresholdTypeConverter
        Inherits TypeConverter

        Public Overrides Function GetPropertiesSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
            Return True
        End Function
        Public Overrides Function GetProperties(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal value As Object, ByVal attributes() As System.Attribute) As System.ComponentModel.PropertyDescriptorCollection
            Return TypeDescriptor.GetProperties(GetType(Threshold))
        End Function
    End Class
#End Region

#Region "Border"
    Public Class BorderType
        Private def_ChromeWidth As Single = 8
        Private def_ChromeColor As Color = Color.Silver
        Private def_RimWidth As Single = 3
        Private def_RimColor As Color = Color.LightGray

        Private _ChromeWidth As Single = def_ChromeWidth
        Private _ChromeColor As Color = def_ChromeColor
        Private _RimWidth As Single = def_RimWidth
        Private _RimColor As Color = def_RimColor

        Public Event PropertyChanged(ByVal PropertyName As String)

        Friend Sub New()
        End Sub

        Public Property ChromeWidth() As Single
            Get
                Return _ChromeWidth
            End Get
            Set(ByVal value As Single)
                If value < 0 Then
                    _ChromeWidth = 0
                Else
                    _ChromeWidth = value
                End If

                RaiseEvent PropertyChanged("ChromeWidth")
            End Set
        End Property
        Public Sub ResetChromeWidth()
            ChromeWidth = def_ChromeWidth
        End Sub
        Public Function ShouldSerializeChromeWidth() As Boolean
            Return Not (ChromeWidth.Equals(def_ChromeWidth))
        End Function

        Public Property ChromeColor() As Color
            Get
                Return _ChromeColor
            End Get
            Set(ByVal value As Color)
                _ChromeColor = value
                RaiseEvent PropertyChanged("ChromeColor")
            End Set
        End Property
        Public Sub ResetChromeColor()
            ChromeColor = def_ChromeColor
        End Sub
        Public Function ShouldSerializeChromeColor() As Boolean
            Return Not (ChromeColor.Equals(def_ChromeColor))
        End Function

        Public Property RimWidth() As Single
            Get
                Return _RimWidth
            End Get
            Set(ByVal value As Single)
                If value < 0 Then
                    _RimWidth = 0
                Else
                    _RimWidth = value
                End If
                RaiseEvent PropertyChanged("RimWidth")
            End Set
        End Property
        Public Sub ResetRimWidth()
            RimWidth = def_RimWidth
        End Sub
        Public Function ShouldSerializeRimWidth() As Boolean
            Return Not (RimWidth.Equals(def_RimWidth))
        End Function

        Public Property RimColor() As Color
            Get
                Return _RimColor
            End Get
            Set(ByVal value As Color)
                _RimColor = value
                RaiseEvent PropertyChanged("RimColor")
            End Set
        End Property
        Public Sub ResetRimColor()
            RimColor = def_RimColor
        End Sub
        Public Function ShouldSerializeRimColor() As Boolean
            Return Not (RimColor.Equals(def_RimColor))
        End Function

        Public Sub Reset()
            _ChromeWidth = def_ChromeWidth
            _ChromeColor = def_ChromeColor
            _RimWidth = def_RimWidth
            _RimColor = def_RimColor
            RaiseEvent PropertyChanged("All")
        End Sub
        Public Function ShouldSerialize() As Boolean
            Return ShouldSerializeChromeWidth() OrElse ShouldSerializeChromeColor() OrElse ShouldSerializeRimWidth() OrElse ShouldSerializeRimColor()
        End Function

        Public Overrides Function ToString() As String
            Return "(Border)"
        End Function
    End Class
    Public Class BorderTypeConverter
        Inherits TypeConverter

        Public Overrides Function GetPropertiesSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
            Return True
        End Function
        Public Overrides Function GetProperties(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal value As Object, ByVal attributes() As System.Attribute) As System.ComponentModel.PropertyDescriptorCollection
            Return TypeDescriptor.GetProperties(GetType(BorderType))
        End Function
    End Class
#End Region

End Class
